﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.IO;
using System.Configuration;

namespace DotNETX.FileServer
{
    [ServiceBehavior(
        InstanceContextMode = InstanceContextMode.PerCall,
        ConcurrencyMode = ConcurrencyMode.Multiple,
        UseSynchronizationContext = false)]
    public class FileManagerService : IFileManagerService
    {
        public const string CONFIG_KEY_ROOT_PATH = "DotNETX.FileServer.FileService.RootPath";
        public const string CONFIG_KEY_TEMP_PATH = "DotNETX.FileServer.FileService.TempPath";

        public static bool IsValid(string vPath)
        {
            if (string.IsNullOrWhiteSpace(vPath) || vPath.Contains("..")) return false;
            vPath = vPath.Replace('/', '\\'); if (vPath.StartsWith("\\")) return false;
            return true;
        }

        public static string Valid2ExistRefPath(string root, string vPath)
        {
            if (!IsValid(vPath)) return null;
            string path = Path.Combine(root, vPath);
            if (!Directory.Exists(path)) return null;

            return path;
        }
        public static string Valid2ExistRefFile(string root, string vFile)
        {
            if (!IsValid(vFile)) return null;
            string path = Path.Combine(root, vFile);
            if (!File.Exists(path)) return null;

            return path;
        }

        public bool HasFile(string vPath)
        {
            string path = Valid2ExistRefFile(RootPath, vPath);
            return null != path;
        }

        public List<string> GetDirs(string vPath)
        {
            string path = Valid2ExistRefPath(RootPath, vPath);
            if (null == path)
                throw new FaultException<FileErrorCode>(FileErrorCode.PathNotExist, new FaultReason(""));

            string[] dirs = Directory.GetDirectories(path);
            return dirs == null ? null : new List<string>(dirs);
        }

        public List<RemoteFileInfo> GetFiles(string vPath)
        {
            string path = Valid2ExistRefPath(RootPath, vPath);
            if (null == path)
                throw new FaultException<FileErrorCode>(FileErrorCode.PathNotExist, new FaultReason(""));

            DirectoryInfo rootInfo = new DirectoryInfo(path);
            FileInfo[] fis = rootInfo.GetFiles();
            if (null == fis || fis.Length == 0) return null;
            List<RemoteFileInfo> lst = new List<RemoteFileInfo>();
            foreach (FileInfo fi in fis)
            {
                lst.Add(new RemoteFileInfo()
                {
                    VirtualPath = fi.FullName.Replace(rootInfo.FullName, ""),
                    Length = fi.Length
                });
            }
            return lst;
        }

        public RemoteFileInfo GetFile(string vFile)
        {
            string path = Valid2ExistRefFile(RootPath, vFile);
            if (null == path)
                throw new FaultException<FileErrorCode>(FileErrorCode.FileNotExist, new FaultReason(""));

            FileInfo fi = new FileInfo(path);
            return new RemoteFileInfo()
            {
                VirtualPath = vFile.StartsWith("\\") ? vFile : vFile + "\\",
                Length = fi.Length
            };
        }

        public FileSectionInfo GetFileSection(string vFile, long offset, int length)
        {
            string path = Valid2ExistRefFile(RootPath, vFile);
            if (null == path)
                throw new FaultException<FileErrorCode>(FileErrorCode.FileNotExist, new FaultReason(""));

            try
            {
                FileSectionInfo fsi = null;
                fsi = new FileSectionInfo() { Content = new byte[length] };
                using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    fs.Seek(offset, SeekOrigin.Begin);
                    fsi.ContentLength = fs.Read(fsi.Content, 0, length);
                    fsi.Offset = offset;
                    fsi.TotalLength = fs.Length;
                }
                return fsi;
            }
            catch (Exception ex)
            {
                throw new FaultException<FileErrorCode>(FileErrorCode.Unknow, new FaultReason(ex.Message));
            }
        }

        public string PutFile(RemoteFileInfo rFile, bool cover)
        {
            if (!IsValid(rFile.VirtualPath))
            {
                throw new FaultException<FileErrorCode>(FileErrorCode.Unknow, new FaultReason("非法路径！"));
            }
            if (File.Exists(Path.Combine(RootPath, rFile.VirtualPath)) && !cover)
            {
                throw new FaultException<FileErrorCode>(FileErrorCode.FileExist, new FaultReason(""));
            }

            string guid = Guid.NewGuid().ToString();
            string file = Path.Combine(TempPath, string.Format("{0}.temp", guid));
            try
            {
                using (FileStream fs = new FileStream(file, FileMode.Create, FileAccess.ReadWrite))
                {
                    fs.SetLength(rFile.Length);
                    //byte[] buffer = new byte[2048];
                    //long i = 0;
                    //while (i < rFile.Length)
                    //{
                    //    long p = rFile.Length - i < 2048 ? rFile.Length -i : 2048;
                    //    fs.Write(buffer, 0,(int)p);
                    //    i += p;
                    //}
                    fs.Flush(true);
                }
                lock (s_LockObj)
                {
                    FileHandleMap.Add(guid, new FileCompleteInfo()
                    {
                        VirtualPath = rFile.VirtualPath,
                        FilePath = file,
                        TotalLength = rFile.Length,
                        CompleteLength = 0,
                        IsCover = cover,
                        IsCancel = false
                    });
                }
                return guid;
            }
            catch (Exception ex)
            {
                // delete temp
                if (File.Exists(file)) File.Delete(file);
                throw new FaultException<FileErrorCode>(FileErrorCode.Unknow, new FaultReason(ex.Message));
            }
        }

        public int PutFileSection(string handle, FileSectionInfo section)
        {
            bool handleExist = false;
            string filepath = null;
            bool isCancel = false;
            lock (s_LockObj)
            {
                handleExist = FileHandleMap.ContainsKey(handle);
                if (handleExist)
                {
                    isCancel = FileHandleMap[handle].IsCancel;
                    filepath = FileHandleMap[handle].FilePath;
                }
                if (isCancel)
                {
                    try
                    {
                        File.Delete(filepath);
                        FileHandleMap.Remove(handle);
                    }
                    finally { }
                }
            }

            if (handleExist && null != filepath)
            {
                if (isCancel) return 0;
                try
                {
                    using (FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
                    {
                        fs.Lock(section.Offset, section.ContentLength);
                        fs.Seek(section.Offset, SeekOrigin.Begin);
                        fs.Write(section.Content, 0, (int)section.ContentLength);
                        fs.Unlock(section.Offset, section.ContentLength);
                    }
                }
                catch (Exception ex)
                {
                    throw new FaultException<FileErrorCode>(FileErrorCode.Unknow, new FaultReason(ex.Message));
                }
                bool isComplete = false;
                string virtualpath = null;
                bool isCover = false;
                lock (s_LockObj)
                {
                    FileCompleteInfo complete = FileHandleMap[handle];
                    complete.CompleteLength += section.ContentLength;

                    if (complete.CompleteLength == complete.TotalLength)
                    {
                        FileHandleMap.Remove(handle);
                        virtualpath = complete.VirtualPath;
                        isCover = complete.IsCover;
                        isComplete = true;
                    }
                }
                if (null != virtualpath && isComplete)
                {
                    if (isCover)
                    {
                        File.Delete(Path.Combine(RootPath, virtualpath));
                        File.Move(filepath, Path.Combine(RootPath, virtualpath));
                    }
                    else
                    {
                        try
                        {
                            File.Move(filepath, Path.Combine(RootPath, virtualpath));
                        }
                        catch (Exception ex)
                        {
                            throw new FaultException<FileErrorCode>(FileErrorCode.FileExist, new FaultReason(ex.Message));
                        }
                    }
                }
                return section.ContentLength;
            }
            throw new FaultException<FileErrorCode>(FileErrorCode.FileHandleNotExist, new FaultReason(""));
        }

        public bool CancelPutFile(string handle)
        {
            try
            {
                lock (s_LockObj)
                {
                    FileHandleMap[handle].IsCancel = true;
                }
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool DeleteFile(string vFile)
        {
            if (string.IsNullOrWhiteSpace(vFile) || vFile.Contains("..")) return false;

            vFile = vFile.Replace('/', '\\'); if (vFile.StartsWith("\\")) return false;
            string filepath = Path.Combine(RootPath, vFile);
            if (!File.Exists(filepath)) return true;

            try
            {
                File.Delete(filepath);
                return true;
            }
            catch (Exception ex)
            {
                throw new FaultException<FileErrorCode>(FileErrorCode.Unknow, new FaultReason(ex.Message));
            }
        }

        public bool CopyFile(string vFile, string vDesFile, bool cover)
        {
            if (string.IsNullOrWhiteSpace(vFile) || vFile.Contains("..")) return false;
            if (string.IsNullOrWhiteSpace(vDesFile) || vDesFile.Contains("..")) return false;

            vFile = vFile.Replace('/', '\\'); if (vFile.StartsWith("\\")) return false;
            vDesFile = vDesFile.Replace('/', '\\'); if (vDesFile.StartsWith("\\")) return false;
            string filepath = Path.Combine(RootPath, vFile);
            string desfilepath = Path.Combine(RootPath, vDesFile);
            if (!File.Exists(filepath)) return false;

            try
            {
                FileInfo fi = new FileInfo(desfilepath);
                if (!Directory.Exists(fi.DirectoryName)) Directory.CreateDirectory(fi.DirectoryName);
                File.Copy(filepath, desfilepath, cover);
                return true;
            }
            catch (Exception ex)
            {
                throw new FaultException<FileErrorCode>(FileErrorCode.Unknow, new FaultReason(ex.Message));
            }
        }

        public bool MoveFile(string vFile, string vDesFile, bool cover)
        {
            if (string.IsNullOrWhiteSpace(vFile) || vFile.Contains("..")) return false;
            if (string.IsNullOrWhiteSpace(vDesFile) || vDesFile.Contains("..")) return false;

            vFile = vFile.Replace('/', '\\'); if (vFile.StartsWith("\\")) return false;
            vDesFile = vDesFile.Replace('/', '\\'); if (vDesFile.StartsWith("\\")) return false;
            string filepath = Path.Combine(RootPath, vFile);
            string desfilepath = Path.Combine(RootPath, vDesFile);
            if (!File.Exists(filepath)) return false;

            try
            {
                FileInfo fi = new FileInfo(desfilepath);
                if (fi.Exists)
                {
                    if (!cover) return false;
                    else File.Delete(desfilepath);
                }

                if (!Directory.Exists(fi.DirectoryName)) Directory.CreateDirectory(fi.DirectoryName);
                File.Move(filepath, desfilepath);
                return true;
            }
            catch (Exception ex)
            {
                throw new FaultException<FileErrorCode>(FileErrorCode.Unknow, new FaultReason(ex.Message));
            }
        }

        #region Static
        static FileManagerService()
        {
            // 读取配置
            if (RootPath == null)
            {
                lock (s_LockObj)
                {
                    if (RootPath == null)
                    {
                        Type type = typeof(FileManagerService);
                        RootPath = TryGetAppSetting(type, CONFIG_KEY_ROOT_PATH);
                        TempPath = TryGetAppSetting(type, CONFIG_KEY_TEMP_PATH);
                        if (!Path.IsPathRooted(RootPath)) RootPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, RootPath);
                        if (!Path.IsPathRooted(TempPath)) TempPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, TempPath);

                        if (!Directory.Exists(RootPath)) Directory.CreateDirectory(RootPath);
                        if (!Directory.Exists(TempPath)) Directory.CreateDirectory(TempPath);
                    }
                }
            }
        }

        private static string TryGetAppSetting(Type type, string key)
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(type.Assembly.Location);
            if (config.AppSettings.Settings.AllKeys.Contains(key))
            {
                return config.AppSettings.Settings[key].Value;
            }
            else
            {
                return ConfigurationManager.AppSettings[key];
            }
        }

        private static string TempPath { get; set; }
        private static string RootPath { get; set; }
        private static Dictionary<string, FileCompleteInfo> FileHandleMap = new Dictionary<string, FileCompleteInfo>();
        private static object s_LockObj = new object();
        #endregion
    }
}
